/**
 * Copyright (c) 2009, Robert Schuster
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 *
 * - Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer in the documentation and/or other
 *   materials provided with the distribution.
 * - Neither the name of Robert Schuster nor the names of his
 *   contributors may be used to endorse or promote products
 *   derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This is the main file of the CLcalc-Library that implements usefull
 * functions for calulation on a OpenCL-Devices
 *
 */

#include <CLcalc.h>
#include <OpenCL/opencl.h>
#include <iostream>
#include <sys/time.h>

#include "Environment.hpp"

using namespace std;
using namespace CLcalc;

Environment* env;

int CLcalc_init() {
    
    try {
        //Create the OpenCL-Environment
        env = new Environment();
                
    } catch (Error er) {
        return er.err_id;
    }
    return 0;

}

int CLcalc_shutdown() {
    delete env;
    return 0;
}

double CLcalc_getTimeInMillis() {
    timeval start;
    gettimeofday(&start,NULL);
    double TimeInMilliseconds = (start.tv_sec*1000000+start.tv_usec) / 1000.0;
    return TimeInMilliseconds;
}

const char* CLcalc_getDeviceName(int device_type) {
    Device* device = env->getDevice(device_type);
    if (device == NULL) {
        return "not available!";
    }

    return device->getDeviceName().c_str();
}

int* CLcalc_getDeviceSpeed(int device_type) {
    int * ergebnis = new int[3];
    Device* device = env->getDevice(device_type);
    if (device == NULL) {
        ergebnis[0] = 0;
        ergebnis[1] = 0;
        ergebnis[2] = 0;
        return ergebnis;
    }

    //get the Speed information
    try {
        ergebnis = device->getDeviceSpeed();
    } catch (Error er) {
        ergebnis[0] = -1;
        ergebnis[1] = er.err_id;
        ergebnis[2] = 0;
        return ergebnis;
    }
    return ergebnis;
}

Error* lastErrorObject = NULL;

const char* CLcalc_getLastErrorText() {
    if (lastErrorObject == NULL) return "No Error!";
    string temp(lastErrorObject->Text);
    if (temp.length() == 0)
        temp = "Unknown Error!";
    return temp.c_str();
}

void* CLcalc_getLastErrorObject() {
    return (void*)lastErrorObject;
}

int CLcalc_Executor_new(int device_type) {
    try {
        return env->createExecutor(device_type);
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_addProgFromSource(int ExecutorID, const char* source, const char* name) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        return ex->addProgFromSource(source, name);
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_addData(int ExecutorID, void* arg1, size_t arg1_size, int arg1_dir) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        return ex->addData(arg1, arg1_size, arg1_dir);
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_setArgument(int ExecutorID, int progID, int arg_pos, int DataID) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->setArgument(progID, arg_pos, DataID);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_setConstIntArgument(int ExecutorID, int progID, int arg_pos, int argument) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->setConstIntArgument(progID, arg_pos, argument);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_Execute(int ExecutorID, int progID, int MaxIndex) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->Execute(progID, MaxIndex);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_getData(int ExecutorID, int DataID, void* data, size_t data_size) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->getData(DataID,data,data_size);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_releaseData(int ExecutorID, int DataID) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->releaseData(DataID);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

void* CLcalc_Executor_getDataHostPtr(int ExecutorID, int DataID){
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        return ex->getDataHostPtr(DataID);
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return NULL;
    }
}

long CLcalc_Executor_getDataDir(int ExecutorID, int DataID) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        return ex->getDataDir(DataID);
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return -1;
    }
}

int CLcalc_Executor_shutdown(int ExecutorID) {
    try {
        env->shutdownExecutor(ExecutorID);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_BuildIn_basic(int ExecutorID, void* arg1, void* arg2, void* result, int* length, int dims, const char* fname, const char* tname) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->BuildIn_basic(arg1, arg2, result, length, dims, fname, tname);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_BuildIn_basic_execOnly(int ExecutorID, int arg1ID, int arg2ID, int resultID, int* length, int dims, const char* fname, const char* tname) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->BuildIn_basic_execOnly(arg1ID, arg2ID, resultID, length, dims, fname, tname);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_BuildIn_CLintrinsics1arg(int ExecutorID, void* input, void* output, int* length, int dims, const char* fname, const char* tname) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->BuildIn_CLintrinsics1arg(input, output, length, dims, fname, tname);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

int CLcalc_Executor_BuildIn_CLintrinsics1arg_execOnly(int ExecutorID, int inputID, int outputID, int* length, int dims, const char* fname, const char* tname) {
    try {
        Executor* ex = env->getExecutor(ExecutorID);
        ex->BuildIn_CLintrinsics1arg_execOnly(inputID, outputID, length, dims, fname, tname);
        return 0;
    } catch (Error er) {
        if (lastErrorObject != NULL) delete lastErrorObject;
        lastErrorObject = new Error(er);
        return er.err_id;
    }
}

